(* Author: Diego Echeverri + Expert FSharp Sample
(Chapter 11. Graph Control)
Comments and bugs to diegoeche@gmail.com *)

//TODO: position labels, mouse navigation... etc, etc
//Usability in FSI

namespace  LambdaMop
#light
open System
open System.Collections.Generic
open System.ComponentModel
open System.Drawing
open System.Drawing.Drawing2D
open System.Data
open System.Text
open System.Windows.Forms

module Plotting =
    type Basic2DPlotMode =
       | Lines = 1
       | Points = 2
       | PointsLines = 3

    type Basic2DPlot(values :(float * float) list) =
        inherit UserControl() as base
        let data = List.sort compare values
        let mutable (center_x, center_y) = (0,0)
        let (max_x, max_y) = 
            (data |> List.map fst |> List.fold1_left max, //there's a better way
             data |> List.map snd |> List.fold1_left max)
        let delta = 10
        let mutable axisColor = Color.White
        let mutable pointColor = Color.White
        let mutable verticalLabelFormat = "{0:F2}"
        let mutable startTime = 0L
        let mutable verticalLines = 0
        let mutable horizontalLabelFormat = "{0:T}"
        let mutable scale = 1.
        let mutable mode = Basic2DPlotMode.Points

        let rightBottomMargin = Size(10, 10)
        let leftTopMargin = Size(10, 10)
        
        do base.BackColor <- Color.DarkBlue
        
        [<Category("Graph Style")>]
        member x.AxisColor
            with get() = axisColor
            and set(v:Color) = axisColor <- v; x.Invalidate()

        [<Category("Graph Style")>]
        member x.Mode
            with get() = mode
            and set(v) = mode <- v; x.Invalidate()

        [<Category("Graph Style")>]
        member x.VerticalLines
            with get() = verticalLines
            and set(v:int) = verticalLines <- v; x.Invalidate()
        
        [<Category("Graph Style")>]
        member x.GraphBackColor
            with get() = x.BackColor
            and set(v:Color) = x.BackColor <- v
  
        [<Category("Graph Style")>]
        member x.PointColor
            with get() = pointColor
            and set(v:Color) = pointColor <- v; x.Invalidate()

        [<Category("Graph Style")>]
        member x.VerticalLabelFormat
            with get() = verticalLabelFormat
            and set(v:string) = verticalLabelFormat <- v; x.Invalidate()
        
        member x.Zoom (amount:int) =
            printfn "Amount %A" amount
            scale <- scale * (0.95 + 0.1 * float amount / 120.) //Change
            printfn "Scale %A" scale            
            x.Invalidate()

        override x.OnKeyDown (e:KeyEventArgs) =
            base.OnKeyDown(e)
            do
                match e with
                | e when e.KeyCode = Keys.Left -> center_x <- center_x + delta
                | e when e.KeyCode = Keys.Right -> center_x <- center_x - delta
                | e when e.KeyCode = Keys.Up -> center_y <- center_y + delta
                | e when e.KeyCode = Keys.Down -> center_y <- center_y - delta
                | e when e.KeyCode = Keys.Oemplus -> scale <- scale * 1.1
                | e when e.KeyCode = Keys.OemMinus -> scale <- scale * 0.9
                | _ -> ()
            x.Invalidate()
            
        override x.IsInputKey(x:Keys) = //Eval All the keys
            true

        override x.OnSizeChanged (e:EventArgs) =
            base.OnSizeChanged(e)
            x.Invalidate()

        override x.OnPaint (e:PaintEventArgs) =
            let g = e.Graphics
            
            use linePen = new Pen(x.AxisColor)
            use pointPen = new Pen(x.PointColor)
            
            let width,height = (x.Width - 4,x.Height - 4)
            
            //get cords based on the scale factor and center pos
            let to_Cord (x,y) = 
                (x * (float width) ,  y * (float height)) 
                |> fun (x,y) -> 
                    (int (x * scale) + center_x, height + int (- y * scale) + center_y) 

            let drawPoint (x,y) =
                let (x,y) = to_Cord(x,y) 
                g.DrawEllipse(pointPen, x-2, y-2, 4, 4) //Fix-Me

            let drawLine ((x1,y1),(x2,y2)) =
                let (x1,y1) = to_Cord(x1,y1)
                let (x2,y2) = to_Cord(x2,y2)
                g.DrawLine(linePen,x1,y1,x2,y2)

            //Draw axis
            drawLine ((1000., 0.),(-1000., 0.))
            drawLine ((0., 1000.),(0., -1000.))
            
            //Draw Points
            if mode = Basic2DPlotMode.Points || 
               mode = Basic2DPlotMode.PointsLines  then
                List.iter drawPoint data

            //Draw Lines
            if mode = Basic2DPlotMode.Lines || 
               mode = Basic2DPlotMode.PointsLines then
                data |> Seq.pairwise 
                |> Seq.iter drawLine 

                
    let plot values = 
        let form = new Form(Text="Example: test",Size=Size(800, 600),Visible=true,TopMost=true)
        let graph = new Basic2DPlot(values, Dock=DockStyle.Fill)
        let properties = new PropertyGrid(Dock=DockStyle.Fill)
        let container = new SplitContainer(Dock=DockStyle.Fill, SplitterDistance=350)

        // We use a split container to divide the area into two parts
        container.Panel1.Controls.Add(graph)
        container.Panel2.Controls.Add(properties)

        // Configure the property grid to display only properties in the
        // category "Graph Style"
        properties.SelectedObject <- graph

        let graphStyleCat = (CategoryAttribute("Graph Style") :> Attribute)
        properties.BrowsableAttributes <- AttributeCollection([| graphStyleCat |])
        form.Controls.Add(container)

        Application.Run(form) 

